using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Pitoshka.Tools.Helpers.TemplateParser
{
    public class Parser
	{
		#region Properties
		private string _BlockId = Guid.NewGuid().ToString();
		internal string BlockId
		{
			get { return _BlockId; }
			set { _BlockId = value; }
		}

        private Dictionary<string, Parser> _Blocks = new Dictionary<string, Parser>();

		private string _Template;
        public string Template
		{
			get { return this._Template; }
			set {  this._Template = value; }
		} 

		private string _ParsedTemplate;
		public string ParsedTemplate
		{
			get { return _ParsedTemplate; }
		}

		private Dictionary<string, object> _Variables = new Dictionary<string,object>();
		public Dictionary<string, object> Variables
        {
            get { return this._Variables; }
            set { this._Variables = value; }
        }		
        
        public Dictionary<string, Parser> Blocks
        {
            get { return _Blocks; }
		}
		#endregion Properties

		#region Contructors
		public Parser()
        {
        }
		public Parser(string Template)
        {
			this.Template = Template;
        }
		public Parser(Dictionary<string, object> Variables)
        {
			this.Variables = Variables;
        }
		public Parser(string Template, Dictionary<string, object> Variables)
        {
			this.Template = Template;
            this.Variables = Variables;
        }
        #endregion

		#region Public methods
		public string Parse()
        {
			LoadBlocks();
			ParseBlocks();

			ParseConditions();
            ParseVariables();
			
			return this.ParsedTemplate;
		}
		#endregion Public methods

		#region Private methods
		private void LoadBlocks()
		{
			this._ParsedTemplate = this.Template;

			//int idxPrevious = 0;
			int idxCurrent = 0;
			while ((idxCurrent = this._ParsedTemplate.IndexOf(Tags.BlockTagBeginBegin, idxCurrent)) != -1)
			{
				string BlockName;
				int idxBlockBeginBegin, idxBlockBeginEnd, idxBlockEndBegin;

				idxBlockBeginBegin = idxCurrent;
				idxCurrent += Tags.BlockTagBeginBegin.Length;

				// Searching for BlockBeginEnd Index
				idxBlockBeginEnd = this._ParsedTemplate.IndexOf(Tags.BlockTagBeginEnd, idxCurrent);
				if (idxBlockBeginEnd == -1) throw new Exception("Could not find BlockTagBeginEnd");


				// Getting Block Name
				BlockName = this._ParsedTemplate.Substring(idxCurrent, (idxBlockBeginEnd - idxCurrent));
				idxCurrent = idxBlockBeginEnd + Tags.BlockTagBeginEnd.Length;


				// Getting End of Block index
				string EndBlockStatment = Tags.BlockTagEndBegin + BlockName + Tags.BlockTagEndEnd;
				idxBlockEndBegin = this._ParsedTemplate.IndexOf(EndBlockStatment, idxCurrent);
				if (idxBlockEndBegin == -1) throw new Exception("Could not find End of Block with name '" + BlockName + "'");


				// Add Block to Dictionary
				Parser block = new Parser();
				block.Template = this._ParsedTemplate.Substring(idxCurrent, (idxBlockEndBegin - idxCurrent));
				if (this.Blocks.ContainsKey(BlockName))
				{
					this.Blocks.Remove(BlockName);
				}
				this.Blocks.Add(BlockName, block);


				// Remove Block Declaration From Template
				this._ParsedTemplate
					= this._ParsedTemplate.Substring(0, idxBlockBeginBegin) 
					+ block.BlockId
					+ this._ParsedTemplate.Substring(idxBlockEndBegin + EndBlockStatment.Length);

				idxCurrent = idxBlockBeginBegin;
			}
		}
		
		private void ParseBlocks()
		{
			foreach (KeyValuePair<string, Parser> blockPair in this.Blocks)
			{				
				//---------------------------------------------
				//--- Trying to get needed collection
				//--- which are mentioned in FOREACH block declaration
				IList values = null;				
				foreach (KeyValuePair<string, object> varPair in this.Variables)
				{
					if (blockPair.Key.StartsWith(varPair.Key))
					{
						try
						{
							object gettedValue = GetValue(blockPair.Key.Substring(blockPair.Key.IndexOf(".") + 1), varPair.Value);
							values = gettedValue as IList;
						}
						catch
						{
						}
					}
				}


				StringBuilder parsedBlockContent = new StringBuilder();
				if (values != null)
				{										
					//---------------------------------------------
					//--- Parse block for every item in list
					for (int i = 0; i < values.Count; i++)
					{
						Dictionary<string, object> variables = new Dictionary<string, object>();
						
						variables.Add("value", values[i]);
						variables.Add("IsFirst", i == 0);
						variables.Add("IsLast", i == values.Count - 1);

						blockPair.Value.Variables = variables;

						parsedBlockContent.Append(blockPair.Value.Parse());
					}
				}

				this._ParsedTemplate = this._ParsedTemplate.Replace(
					blockPair.Value.BlockId
					, parsedBlockContent.ToString());
			}
		}                
        private void ParseConditions()
        {
            //int idxPrevious = 0;
            int idxCurrent = 0;
			//while ((idxCurrent = this._ParsedTemplate.IndexOf(Tags.ConditionTagIfBegin, idxCurrent)) != -1)
			while ((idxCurrent = this._ParsedTemplate.IndexOf(Tags.ConditionTagIfBegin)) != -1)
            {
				string VarName, VarNameFirstObject;
                string TrueBlock, FalseBlock;
                string ElseStatment, EndIfStatment;
                int idxIfBegin, idxIfEnd, idxElseBegin, idxEndIfBegin;
                bool boolValue;

                idxIfBegin = idxCurrent;
				idxCurrent += Tags.ConditionTagIfBegin.Length;

                // Searching for EndIf Index

				idxIfEnd = this._ParsedTemplate.IndexOf(Tags.ConditionTagIfEnd, idxCurrent);
                if (idxIfEnd == -1) throw new Exception("Could not find ConditionTagIfEnd");

                // Getting Value Name

                VarName = this._ParsedTemplate.Substring(idxCurrent, (idxIfEnd-idxCurrent));
				if (VarName.Contains("."))
					VarNameFirstObject = VarName.Substring(0, VarName.IndexOf("."));
				else
					VarNameFirstObject = VarName;

				idxCurrent = idxIfEnd + Tags.ConditionTagIfEnd.Length;

                // Compare ElseIf and EndIf Indexes

				ElseStatment = Tags.ConditionTagElseBegin + VarName + Tags.ConditionTagElseEnd;
				EndIfStatment = Tags.ConditionTagEndIfBegin + VarName + Tags.ConditionTagEndIfEnd;
                idxElseBegin = this._ParsedTemplate.IndexOf(ElseStatment, idxCurrent);
                idxEndIfBegin = this._ParsedTemplate.IndexOf(EndIfStatment, idxCurrent);
                if (idxElseBegin > idxEndIfBegin) throw new Exception("Condition Else Tag placed after Condition Tag EndIf for '"+VarName+"'");

                // Getting True and False Condition Blocks

                if (idxElseBegin != -1)
                {
                    TrueBlock = this._ParsedTemplate.Substring(idxCurrent, (idxElseBegin-idxCurrent));
                    FalseBlock = this._ParsedTemplate.Substring((idxElseBegin+ElseStatment.Length), (idxEndIfBegin-idxElseBegin-ElseStatment.Length));
                }
                else
                {
                    TrueBlock = this._ParsedTemplate.Substring(idxCurrent, (idxEndIfBegin-idxCurrent));
                    FalseBlock = "";
                }

                // Parse Condition

                try
                {
					object VarValue = null;
					if (this.Variables.ContainsKey(VarNameFirstObject) && this.Variables[VarNameFirstObject] != null)
					{
						if (this.Variables[VarNameFirstObject] is string)
						{
							VarValue = this.Variables[VarNameFirstObject].ToString();
						}
						else if (this.Variables[VarNameFirstObject] is Boolean)
						{
							VarValue = this.Variables[VarNameFirstObject];
						}
						else
						{
							VarValue = GetValue(VarName.Replace(VarNameFirstObject + ".", ""), this.Variables[VarNameFirstObject]);
						}
					}



					if (VarValue is Boolean)
					{
						boolValue = (bool) VarValue;
					}
					else if (VarValue is string)
					{
						boolValue = ((string)VarValue).Trim().Length > 0;
					}
					else
					{
						boolValue = VarValue != null;
					}
                }
                catch
                {
                    boolValue = false;
                }

                //string BeforeBlock = this._ParsedTemplate.Substring(idxPrevious, (idxIfBegin-idxPrevious));

                if (boolValue)
                {
					this._ParsedTemplate
						= this._ParsedTemplate.Substring(0, idxIfBegin)
						+ TrueBlock
						+ this._ParsedTemplate.Substring(idxEndIfBegin + EndIfStatment.Length);
                }
                else
                {
					this._ParsedTemplate
						= this._ParsedTemplate.Substring(0, idxIfBegin)
						+ FalseBlock
						+ this._ParsedTemplate.Substring(idxEndIfBegin + EndIfStatment.Length);

                }

                //idxCurrent = idxEndIfBegin + EndIfStatment.Length;
                //idxPrevious = idxCurrent;
            }            
        }
        private void ParseVariables()
        {
            int idxCurrent = 0;
			while ((idxCurrent = this._ParsedTemplate.IndexOf(Tags.VariableTagBegin, idxCurrent)) != -1)
            {
				string VarName, VarNameFirstObject, VarValue;
                int idxVarTagEnd;
				

				idxVarTagEnd = this._ParsedTemplate.IndexOf(Tags.VariableTagEnd, (idxCurrent + Tags.VariableTagBegin.Length));
                if (idxVarTagEnd == -1) throw new Exception(String.Format("Index {0}: could not find Variable End Tag", idxCurrent));

                // Getting Variable Name

				VarName = this._ParsedTemplate.Substring((idxCurrent + Tags.VariableTagBegin.Length), (idxVarTagEnd - idxCurrent - Tags.VariableTagBegin.Length));
				if (VarName.Contains("."))
					VarNameFirstObject = VarName.Substring(0, VarName.IndexOf("."));
				else
					VarNameFirstObject = VarName;

                // Checking for Modificators

				string[] VarParts = VarName.Split(Tags.ModificatorTag.ToCharArray());
                VarName = VarParts[0];

                // Getting Variable Value
                // If Variable doesn't exist in _hstValue then
                // Variable Value equal empty string

                
				VarValue = String.Empty;
				if (this.Variables.ContainsKey(VarNameFirstObject) && this.Variables[VarNameFirstObject] != null)
                {
					if (this.Variables[VarNameFirstObject] is string)
					{
						VarValue = this.Variables[VarNameFirstObject].ToString();
					}
					else
					{
						object gettedValue = GetValue(VarName.Replace(VarNameFirstObject + ".", ""), this.Variables[VarNameFirstObject]);
						if (gettedValue != null)
						{
							VarValue = gettedValue.ToString();
						}
					}
                }

                // Apply All Modificators to Variable Value
                for (int i = 1; i < VarParts.Length; i++)
					this.ApplyModificator(ref VarValue, VarParts[i]);

                // Replace Variable in Template
				this._ParsedTemplate = this._ParsedTemplate.Substring(0, idxCurrent) + VarValue + this._ParsedTemplate.Substring(idxVarTagEnd + Tags.VariableTagEnd.Length);

                // Add Length of added value to Current index 
                // to prevent looking for variables in the added value
                // Fixed Date: April 5, 2006
                idxCurrent += VarValue.ToString().Length;
            }
        }

        private void ApplyModificator(ref string Value, string Modificator)
        {
            // Checking for parameters
            
            string strModificatorName = "";
            string strParameters = "";
            int idxStartBrackets, idxEndBrackets;
            if ((idxStartBrackets = Modificator.IndexOf("(")) != -1) {
                idxEndBrackets = Modificator.IndexOf(")", idxStartBrackets);
                if (idxEndBrackets == -1)
                {
                    throw new Exception("Incorrect modificator expression");
                }
                else
                {
                    strModificatorName = Modificator.Substring(0, idxStartBrackets).ToUpper();             
                    strParameters = Modificator.Substring(idxStartBrackets+1, (idxEndBrackets-idxStartBrackets-1));
                }
            }
            else
            {
                strModificatorName = Modificator.ToUpper();
            }
			string[] arrParameters = strParameters.Split(Tags.ModificatorParamSep.ToCharArray());
            for (int i = 0; i < arrParameters.Length; i++)
                arrParameters[i] = arrParameters[i].Trim();

            try
            {
                Type typeModificator = Type.GetType("TemplateParser.Modificators." + strModificatorName);
                if (typeModificator.IsSubclassOf(Type.GetType("TemplateParser.Modificators.Modificator")))
                {
                    Modificator objModificator = (Modificator)Activator.CreateInstance(typeModificator);
                    objModificator.Apply(ref Value, arrParameters);
                }
            }
            catch
            {
                throw new Exception(String.Format("Could not find modificator '{0}'", strModificatorName));
            }
		}
		private object GetValue(string varFullNameName, object sourceObject)
		{
			if (sourceObject == null)
			{
				return null;
			}

			object result = String.Empty;
			
			bool isLast = !varFullNameName.Contains(".");
			string currentPropertyName
				= !isLast
				? varFullNameName.Substring(0, varFullNameName.IndexOf("."))
				: varFullNameName;

			PropertyInfo[] objectProperties = sourceObject.GetType().GetProperties();
			foreach (PropertyInfo info in objectProperties)
			{
				if (info == null
					|| !info.CanRead
					|| info.MemberType != MemberTypes.Property)
					continue;

				if (info.Name == currentPropertyName)
				{
					object propertyValue = info.GetValue(sourceObject, null);

					result = isLast
						? propertyValue
						: GetValue(varFullNameName.Substring(varFullNameName.IndexOf(".") + 1), propertyValue);

					break;
				}
			}

			return result;
		}
		#endregion Private methods

		#region File helpers
		public bool ParseToFile(string FilePath, bool ReplaceIfExists)
		{
			if (File.Exists(FilePath) && !ReplaceIfExists)
			{
				return false;
			}
			else
			{
				StreamWriter sr = File.CreateText(FilePath);
				sr.Write(Parse());
				sr.Close();
				sr.Dispose();

				return true;
			}
		}
		public void ReadTemplateFromFile(string FilePath)
		{
			if (!File.Exists(FilePath))
			{
				throw new ArgumentException("Template file does not exist.");
			}

			StreamReader reader = new StreamReader(FilePath);
			this.Template = reader.ReadToEnd();
			reader.Close();
		}
		#endregion
	}
}